/**
 *    Copyright 2009-2019 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.executor.resultset;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.*;

import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.*;

/**
 * JDBC ResultSet包装器
 *
 * @author Iwao AVE!
 */
public class ResultSetWrapper {
    /**
     * JDBC ResultSet对象，维护了数据库数据引用
     */
    private final ResultSet resultSet;
    /**
     * 类型转换处理器注册表，将会用于处理数据库数据和java对象之间的转换工作。
     */
    private final TypeHandlerRegistry typeHandlerRegistry;
    /**
     * 真实列名称集合
     */
    private final List<String> columnNames = new ArrayList<>();
    /**
     * java类型名称集合
     */
    private final List<String> classNames = new ArrayList<>();
    /**
     * JDBC类型集合
     */
    private final List<JdbcType> jdbcTypes = new ArrayList<>();
    /**
     * 类型转换器集合
     */
    private final Map<String, Map<Class<?>, TypeHandler<?>>> typeHandlerMap = new HashMap<>();
    /**
     * 已配置映射关系的列名和属性名称对应的关系表
     */
    private final Map<String, List<String>> mappedColumnNamesMap = new HashMap<>();
    /**
     * 尚未映射的列名和属性名称对应的关系表
     */
    private final Map<String, List<String>> unMappedColumnNamesMap = new HashMap<>();

    public ResultSetWrapper(ResultSet rs, Configuration configuration) throws SQLException {
        super();
        // 初始化类型处理器注册表
        this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
        // 初始化响应结果
        this.resultSet = rs;
        // 获取ResultSet元数据
        final ResultSetMetaData metaData = rs.getMetaData();
        // 获取返回的列数量
        final int columnCount = metaData.getColumnCount();

        for (int i = 1; i <= columnCount; i++) {
            // 添加列名称集合
            columnNames.add(
                    configuration.isUseColumnLabel()
                            ? metaData.getColumnLabel(i) /*使用列标签*/
                            : metaData.getColumnName(i)/*使用列名称*/
            );
            // 添加jdbc类型
            jdbcTypes.add(JdbcType.forCode(metaData.getColumnType(i)));
            // 添加java类名称
            classNames.add(metaData.getColumnClassName(i));
        }
    }

    public ResultSet getResultSet() {
        return resultSet;
    }

    public List<String> getColumnNames() {
        return this.columnNames;
    }

    public List<String> getClassNames() {
        return Collections.unmodifiableList(classNames);
    }

    public List<JdbcType> getJdbcTypes() {
        return jdbcTypes;
    }

    public JdbcType getJdbcType(String columnName) {
        for (int i = 0; i < columnNames.size(); i++) {
            if (columnNames.get(i).equalsIgnoreCase(columnName)) {
                return jdbcTypes.get(i);
            }
        }
        return null;
    }

    /**
     * Gets the type handler to use when reading the result set.
     * Tries to get from the TypeHandlerRegistry by searching for the property type.
     * If not found it gets the column JDBC type and tries to get a handler for it.
     *
     * @param propertyType
     * @param columnName
     * @return
     */
    public TypeHandler<?> getTypeHandler(Class<?> propertyType, String columnName) {
        TypeHandler<?> handler = null;
        Map<Class<?>, TypeHandler<?>> columnHandlers = typeHandlerMap.get(columnName);
        if (columnHandlers == null) {
            columnHandlers = new HashMap<>();
            typeHandlerMap.put(columnName, columnHandlers);
        } else {
            handler = columnHandlers.get(propertyType);
        }
        if (handler == null) {
            JdbcType jdbcType = getJdbcType(columnName);
            // 从类型转换处理器中获取指定java类型和jdbc类型的类型转换处理器
            handler = typeHandlerRegistry.getTypeHandler(propertyType, jdbcType);
            // Replicate logic of UnknownTypeHandler#resolveTypeHandler
            //            // See issue #59 comment 10
            if (handler == null || handler instanceof UnknownTypeHandler) {
                final int index = columnNames.indexOf(columnName);
                final Class<?> javaType = resolveClass(classNames.get(index));
                if (javaType != null && jdbcType != null) {
                    handler = typeHandlerRegistry.getTypeHandler(javaType, jdbcType);
                } else if (javaType != null) {
                    handler = typeHandlerRegistry.getTypeHandler(javaType);
                } else if (jdbcType != null) {
                    handler = typeHandlerRegistry.getTypeHandler(jdbcType);
                }
            }
            if (handler == null || handler instanceof UnknownTypeHandler) {
                handler = new ObjectTypeHandler();
            }
            columnHandlers.put(propertyType, handler);
        }
        return handler;
    }

    private Class<?> resolveClass(String className) {
        try {
            // #699 className could be null
            if (className != null) {
                return Resources.classForName(className);
            }
        } catch (ClassNotFoundException e) {
            // ignore
        }
        return null;
    }

    /**
     * @param resultMap
     * @param columnPrefix
     * @throws SQLException
     */
    private void loadMappedAndUnmappedColumnNames(ResultMap resultMap, String columnPrefix) throws SQLException {
        // 已经映射的列名集合
        List<String> mappedColumnNames = new ArrayList<>();
        // 未映射的列名集合
        List<String> unmappedColumnNames = new ArrayList<>();

        // 获取大写的列名前缀
        final String upperColumnPrefix = columnPrefix == null ? null : columnPrefix.toUpperCase(Locale.ENGLISH);
        // 根据用户配置的列名称添加前缀后生成生成真正的列名集合，即插入了前缀的列名集合，
        // 注意如果没有指定前缀，该集合为原始的列名集合。
        final Set<String> mappedColumns = prependPrefixes(resultMap.getMappedColumns(), upperColumnPrefix);

        // 处理所有的列名称/列标签
        // columnNames=当前返回结果对象中所有真实的列名称
        for (String columnName : columnNames) {
            // 获取真实列名的大写化字符串
            final String upperColumnName = columnName.toUpperCase(Locale.ENGLISH);

            // 判断指定前缀
            if (mappedColumns.contains(upperColumnName)) {
                // 完成映射
                mappedColumnNames.add(upperColumnName);
            } else {
                // 未完成映射
                unmappedColumnNames.add(columnName);
            }
        }
        // 指定列名前缀对应的已映射的所有字段名称
        mappedColumnNamesMap.put(getMapKey(resultMap, columnPrefix), mappedColumnNames);
        // 指定列名前缀对应的未映射的所有字段名称
        unMappedColumnNamesMap.put(getMapKey(resultMap, columnPrefix), unmappedColumnNames);
    }

    public List<String> getMappedColumnNames(ResultMap resultMap, String columnPrefix) throws SQLException {
        List<String> mappedColumnNames = mappedColumnNamesMap.get(getMapKey(resultMap, columnPrefix));
        if (mappedColumnNames == null) {
            // 加载并映射未处理的列名集合
            loadMappedAndUnmappedColumnNames(resultMap, columnPrefix);
            // 赋值
            mappedColumnNames = mappedColumnNamesMap.get(getMapKey(resultMap, columnPrefix));
        }
        return mappedColumnNames;
    }

    public List<String> getUnmappedColumnNames(ResultMap resultMap, String columnPrefix) throws SQLException {
        List<String> unMappedColumnNames = unMappedColumnNamesMap.get(getMapKey(resultMap, columnPrefix));
        if (unMappedColumnNames == null) {
            // 加载已经映射的和未映射的列名称关系
            loadMappedAndUnmappedColumnNames(resultMap, columnPrefix);
            // 获取映射关系
            unMappedColumnNames = unMappedColumnNamesMap.get(getMapKey(resultMap, columnPrefix));
        }
        return unMappedColumnNames;
    }

    /**
     * 获取映射关系的key值
     *
     * @param resultMap    所属的ResultMap
     * @param columnPrefix 使用的列前缀
     */
    private String getMapKey(ResultMap resultMap, String columnPrefix) {
        return resultMap.getId() + ":" + columnPrefix;
    }

    private Set<String> prependPrefixes(Set<String> columnNames, String prefix) {
        if (columnNames == null || columnNames.isEmpty() || prefix == null || prefix.length() == 0) {
            return columnNames;
        }
        final Set<String> prefixed = new HashSet<>();
        for (String columnName : columnNames) {
            // 追加前缀
            prefixed.add(prefix + columnName);
        }
        return prefixed;
    }

}
